/*
 * Copyright 2011 Ed Clark
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package meander.Uno.Impress

import com.sun.star.beans.XPropertySet
import com.sun.star.drawing.XShape
import com.sun.star.awt.Point
import com.sun.star.awt.Size

import static meander.PagePlacement.*
import meander.Uno.UnoCategory
import meander.Uno.UnoUtils
import com.sun.star.drawing.TextHorizontalAdjust
import com.sun.star.drawing.TextVerticalAdjust
import org.apache.tools.ant.taskdefs.XmlProperty
import com.sun.star.drawing.XShapes
import com.sun.star.drawing.XShapeGrouper
import com.sun.star.uno.UnoRuntime

/**
 * Proxy object for an Impress slide
 */
class Page {
    def events
    def nodes
    def styles
    def strings
    def options
    def pageModel

    def pagesSupplier
    def page

    def xShape
    def xPageProps
    int pageWidth, pageHeight, nShape
    int topM, rightM, bottomM, leftM

    private int borderTop, borderRight, borderBottom, borderLeft
    int nodeWidth
    def nodeXPos = []
    int vbuffer = 100i

    int xpos = 0, ypos = 0, currYpos = 0, maxYpos, minYpos

    Page( named = [:], index = -1) {
        def xDrawPages

        named.each { key, value -> this[ key] = value }

        xDrawPages = pagesSupplier.getDrawPages()

        use (UnoCategory) {
            if ( index < 0) {
                // add a new page
                page = xDrawPages.insertNewByIndex( -1)
//                println 'page layout = ' + page.uno(XPropertySet)['Layout']
            } else {
                page = xDrawPages.getByIndex( index)
                page.uno(XPropertySet)['Layout'] = 20  // should be the 'blank' layout
            }

            borderTop = page.uno(XPropertySet)['BorderTop']
            borderRight = page.uno(XPropertySet)['BorderRight']
            borderBottom = page.uno(XPropertySet)['BorderBottom']
            borderLeft = page.uno(XPropertySet)['BorderLeft']

            pageWidth = page.uno(XPropertySet)["Width"]
            //      pageWidth = (pageWidth * 2.834646f / 100) as int     // convert to 'points'
            pageHeight = page.uno(XPropertySet)["Height"]
            //      pageHeight = (pageHeight * 2.834646f / 100) as int   // convert to 'points'

            setupPage()
        }
    }

/*  Page() {
    def xDrawPages = pagesSupplier.getDrawPages()

    use (UnoCategory) {
      page = xDrawPages.getByIndex( index)

      pageWidth = page.uno(XPropertySet)["Width"]
//      pageWidth = (pageWidth * 2.834646f / 100) as int     // convert to 'points'
      pageHeight = page.uno(XPropertySet)["Height"]
//      pageHeight = (pageHeight * 2.834646f / 100) as int   // convert to 'points'

      setupPage()
    }
  }  */

    public setupPage() {
        XShape rect, text
        def style
        def size, maxX, maxY
        def nodeList = []
        int headerH      // height of the header

        // get the user margins and add in the page borders
        topM = (int)(pageModel.top * 100 / 2.834646) + borderTop
        rightM = (int)(pageModel.right * 100 / 2.834646) + borderRight
        bottomM = (int)(pageModel.bottom * 100 / 2.834646) + borderBottom
        leftM = (int)(pageModel.left * 100 / 2.834646) + borderLeft

        // look for items to put at the top of the page
        // top left
        ypos = topM
        xpos = leftM
        maxX = 0
        strings.strings.each { str ->
            if ( (strings."$str") &&
                 (strings."${str}Str" != '') &&
                 (strings."${str}Pos" == TOPLEFT)) {
                def ts
                if ( str == 'date') ts = handleDateStr( strings."${str}Str")
                else ts =
                style = styles[ strings."${str}Style"]
                text = UnoText.drawCentered( handleString( strings."${str}Str"),
                                             page,
                                             pagesSupplier,
                                             style,
                                             new Point( xpos, ypos)
                                           )
                size = text.getSize()
                if (maxX < size.Width + leftM) maxX = size.Width + leftM
                ypos += size.Height
                if ( ypos > maxY) maxY = ypos
            }
        }

        // top right
        // ypos = topM
        xpos = pageWidth - rightM
        strings.strings.each { str ->
            if ( (strings."$str") &&
                 (strings."${str}Str" != '') &&
                 (strings."${str}Pos" == TOPRIGHT)) {
                style = styles[ strings."${str}Style"]
                text = UnoText.drawCentered( handleString( strings."${str}Str"),      //String str, page, pagesSupplier, Style style, options, Point ip
                                             page,
                                             pagesSupplier,
                                             style,
                                             new Point( xpos, ypos)
                                           )
                size = text.getSize()
                text.setPosition( new Point( xpos - size.Width, ypos))
                if ( size.Width + rightM > maxX) maxX = size.Width + rightM
                ypos += size.Height
                if ( ypos > maxY) maxY = ypos
            }
        }

        // top center
        ypos = topM
        xpos = (int)( (pageWidth - leftM - rightM)/2 + leftM)
        strings.strings.each { str ->
            if ( (strings."${str}Pos" == TOPCENTER) &&
                 (strings."${str}Str" != '') &&
                 (strings."$str")) {
                style = styles[ strings."${str}Style"]
                text = UnoText.drawCentered( handleString( strings."${str}Str"),
                                             page,
                                             pagesSupplier,
                                             style,
                                             new Point( xpos, ypos)
                                           )
                size = text.getSize()
                ypos += size.Height
                if ( ypos > maxY) maxY = ypos
            }
        }
        currYpos = ( maxY > ypos) ? maxY : ypos


        // look for items to put at the bottom of the page
        // bottom left
        ypos = pageHeight - bottomM
        maxYpos = ypos
        xpos = leftM
        maxX = 0
        maxY = ypos
        strings.strings.each { str ->
            if ( (strings."${str}Pos" == BOTTOMLEFT) &&
                 (strings."${str}Str" != '') &&
                 (strings."$str")) {
                style = styles[ strings."${str}Style"]
                text = UnoText.drawCentered( handleString( strings."${str}Str"),
                                             page,
                                             pagesSupplier,
                                             style,
                                             new Point( xpos, ypos)
                                           )
                size = text.getSize()
                if ( maxX < size.Width + leftM ) maxX = size.Width + leftM
                text.setPosition( new Point( xpos, ypos - size.Height))
                ypos = ypos - size.Height
                if ( ypos < maxY) maxY = ypos
            }
        }

        // bottom right
        ypos = pageHeight - bottomM
        xpos = pageWidth - rightM
        strings.strings.each { str ->
            if ( (strings."${str}Pos" == BOTTOMRIGHT) &&
                 (strings."${str}Str" != '') &&
                 (strings."$str")) {
                style = styles[ strings."${str}Style"]
                text = UnoText.drawCentered( handleString( strings."${str}Str"),
                                             page,
                                             pagesSupplier,
                                             style,
                                             new Point( xpos, ypos)
                                           )
                size = text.getSize()
                if ( size.Width + rightM > maxX) maxX = size.Width + rightM
                text.setPosition( new Point( xpos - size.Width, ypos - size.Height))
                ypos = ypos - size.Height
                if ( ypos < maxY) maxY = ypos
            }
        }

        // bottom center
        ypos = pageHeight - bottomM
        xpos = (int)( (pageWidth - leftM - rightM)/2 + leftM)
        strings.strings.each { str ->
            if ( (strings."${str}Pos" == BOTTOMCENTER) &&
               (strings."${str}Str" != '') &&
               (strings."$str")) {
                style = styles[ strings."${str}Style"]
                text = UnoText.drawCentered( handleString( strings."${str}Str"),
                                             page,
                                             pagesSupplier,
                                             style,
                                             new Point( xpos, ypos)
                                           )
                size = text.getSize()
                text.setPosition( new Point( (int)(xpos - size.Width/2), ypos - size.Height))
                ypos = ypos - size.Height
                if ( ypos < maxY) maxY = ypos
            }
        }
        maxYpos = (maxY < ypos) ? maxY : ypos

        // start in on the nodes - if the node list is not null/empty
        int hgt = 0
        if (nodes?.size()) {
            ypos = currYpos
            nodeWidth = (pageWidth - leftM - rightM)  / nodes.size()
            style = styles[ 'node']

            // calculate the x position for each node
            nodes.eachWithIndex { node, i ->
                nodeXPos[i] = (int)((nodeWidth * i) + (nodeWidth / 2) + leftM)
        //        nodeXPos[i] = ((nodeXPos[i] * 100) / 2.834646f ) as int      // convert to '100th mm'
            }

            // draw the nodes
            nodes.eachWithIndex { node, i ->
                xpos = nodeXPos[i]

                rect = UnoUtils.insertBorder( pagesSupplier, page, new Point( xpos, ypos), null, style)

                text = UnoText.drawCentered( node,
                                             page,
                                             pagesSupplier,
                                             style,
                                             new Point( xpos, ypos)
                                           )

                // reposition and resize the border to be around the text
                rect.setSize( text.getSize())
                rect.setPosition( text.getPosition())

                // group the text and the rectangle together
                def group = UnoUtils.buildGroup( pagesSupplier, page, [ rect, text] )
                nodeList << group
            }
            // finally align the bottoms of the nodes
            nodeList.each { n ->
                if( n.getSize().Height > hgt) hgt = n.getSize().Height
            }
            nodeList.each { n ->
                int oldH = n.getSize().Height
                if( oldH < hgt) {
                    Point oldP = n.getPosition()
                    n.setPosition( new Point( oldP.X, oldP.Y + (hgt - oldH)))
                }
            }
        }

        // position the insertion point for the rest of the flow and save this point for
        // use when finalizing the page
        minYpos = currYpos + hgt
        currYpos += hgt + vbuffer
    }

    public finalizePage( boolean lastFlowPage) {
        def style
        /*
         * if the node list is null, then this page doesn't have any events
         * drawn on it - it is a post flow page
         *
         * If the node list has content, then use it to fill nodal lines
         *
         * Also, if the lastFlowPage parameter is true, then the lines only go to the bottom of
         * the last event instead of to the bottom of the page
         */


        // put in the nodal lines using the minYpos value saved during page setup
        Size sz = lastFlowPage ? new Size( 0, currYpos - minYpos) : new Size( 0, maxYpos - minYpos)
        nodeXPos.each { x ->
            def nodeline
            style = styles[ 'node']
            nodeline = UnoUtils.insertLine( pagesSupplier,
                                            page,
                                            new Point( x, minYpos),
                                            sz,
                                            style.border.color.RGB,
                                            (int)(style.border.thickness * 25.4),
                                            style.graphic.line
                                          )
            // push it to the bottom in Z-order
            UnoUtils.setZOrder( nodeline, 0)
        }

        // add any required watermark
        if ( (strings."watermark") &&
             (strings."watermarkStr" != '')) {
            style = styles[ 'watermark']
            def wm = UnoUtils.insertWatermark( strings."watermarkStr",
                                               pagesSupplier,
                                               page,
                                               new Point( 2540i, 2540i),
                                               new Size( pageWidth - 5080, pageHeight - 5080),
                                               [ color: style.text.color, face: style.text.face]
                                             )
            UnoUtils.setZOrder( wm, 0)
        }
    }

    public addEvent( eventObj) {
//        def graphic
//        graphic = eventObj.draw( page, pagesSupplier, nodes, nodeXPos, currYpos)
//        currYpos += graphic.getSize().Height + vbuffer
//        return [ graphic, currYpos]
        def graphics
        def i = 0
        graphics = eventObj.draw( page, pagesSupplier, nodes, nodeXPos, currYpos)
        for (gr in graphics) {
            currYpos += gr.getSize().Height + vbuffer
            // if the graphic fits on the page remove it from the todo list
            if ( currYpos > maxYpos) break // done with graphics on this page
            i++
        }

        // return the graphics list starting after the last successfully place graphic
        // it will be null if everything was placed
        return [ (i >= graphics.size()) ? [] : graphics[ i .. -1], currYpos]
    }

    public addGraphic( graphic) {
        UnoUtils.addShape( page, graphic)
        graphic.setPosition( new Point( graphic.getPosition().X, currYpos))
        currYpos += graphic.getSize().Height + vbuffer
    }

    public void repositionShapes( XShapes shapes) {
        /* if there is more than one shape in there, we will group them together,
         * move them en masse to the currYpos, and then ungroup them
         *
         * if there's only one, then we can just move that shape
         */
        if ( shapes.getCount() > 1) {
            XShape group
            def shapeList = []

            ( 0 .. shapes.getCount() - 1).each { i ->
                def shape = (XShape) UnoRuntime.queryInterface( XShape.class, shapes.getByIndex( i) )
                shapeList.add shape
            }
            group = UnoUtils.buildGroup( pagesSupplier, page, shapeList )
            group.setPosition( new Point( group.getPosition().X, currYpos))
            currYpos += group.getSize().Height + vbuffer
            page.ungroup( group)
        } else {
            XShape shape
            shape = (XShape) UnoRuntime.queryInterface( XShape.class, shapes.getByIndex( 0) )

            shape.setPosition( new Point( shape.getPosition().X, currYpos))
            currYpos += shape.getSize().Height + vbuffer
        }
    }

    public addGraphics( graphics) {
        graphics.each { shape ->
            UnoUtils.addShape( page, shape)
            shape.setPosition( new Point( shape.getPosition().X, currYpos))
            currYpos += shape.getSize().Height + vbuffer
        }
        return [ graphic, currYpos]
    }

    public addNote( noteObj) {
        def graphic
        graphic = noteObj.draw( page, pagesSupplier, currYpos)
        currYpos += graphic.getSize().Height
        return [ graphic, currYpos]
    }

    public delete( shape) {
        UnoUtils.deleteShape( page, shape)
    }

    private strText = { full,f, str, l ->
        switch (str) {
        case 'PC':
        case 'FPC':
        case 'today':
        case 'now':
            return '' + f + strings.counts[ str] + l
            break
        }
        return '' + f + strings.counts[ 'raw'].format( str) + l
    }

    private extractRE = /(?m)(.*)<(.*)>(.*)/           // multi-line pattern looking for something in "< >"

    public handleString (String str) {
        while ( str =~ extractRE) {
            str = str.replaceAll( extractRE, strText)
        }
        return str
    }
}
